/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.lyh.api.asm;

import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_SUPER;
import static org.objectweb.asm.Opcodes.ACC_VARARGS;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Opcodes.V1_1;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.AccessController;
import java.security.PrivilegedAction;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;

/**
 * @author shoushen.luan
 * @date 2022/2/22 2:31 下午
 */
public class ThreadHelper {
    protected static final Spinner SPINNER;

    public abstract static class Spinner {

        public abstract void onSpinWait();
    }

    private static class DefaultSpinner extends Spinner {
        @Override
        public void onSpinWait() {
            Thread.yield();
        }
    }

    static {
        final Object maybeException =
                AccessController.doPrivileged(
                        (PrivilegedAction<Object>)
                                () -> {
                                    try {
                                        // noinspection JavaReflectionMemberAccess
                                        Thread.class.getDeclaredMethod("onSpinWait");
                                        return null;
                                    } catch (final NoSuchMethodException | SecurityException e) {
                                        return e;
                                    }
                                });
        if (maybeException == null) {
            SPINNER = createSpinner();//TODO jdk 1.9+执行
        } else {
            SPINNER = new DefaultSpinner();
        }
    }

    public static void onSpinWait() {
        SPINNER.onSpinWait();
    }

    /**
     * 动态生成JDK 1.9 API实现方法: Thread.onSpinWait()
     *
     * @return
     */
    private static Spinner createSpinner() {
        final String superClassName = Spinner.class.getName();
        final String superClassNameInternal = superClassName.replace('.', '/');
        // 动态生成Class
        final String spinnerClassName = superClassName + "Impl$asm";
        final String spinnerClassNameInternal = spinnerClassName.replace('.', '/');

        final String threadClassNameInternal = Thread.class.getName().replace('.', '/');
        try {
            final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
            cw.visit(
                    V1_1,
                    ACC_PUBLIC + ACC_SUPER,
                    spinnerClassNameInternal,
                    null,
                    superClassNameInternal,
                    null);

            MethodVisitor mv;

            // default constructor
            {
                mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
                mv.visitCode();
                mv.visitVarInsn(ALOAD, 0);
                mv.visitMethodInsn(INVOKESPECIAL, superClassNameInternal, "<init>", "()V", false);
                mv.visitInsn(RETURN);
                mv.visitMaxs(0, 0);
                mv.visitEnd();
            }

            // implementation of method: `public abstract void onSpinWait()`
            {
                mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, "onSpinWait", "()V", null, null);
                mv.visitCode();
                mv.visitMethodInsn(INVOKESTATIC, threadClassNameInternal, "onSpinWait", "()V", false);
                mv.visitInsn(RETURN);
                mv.visitMaxs(0, 0);
                mv.visitEnd();
            }
            cw.visitEnd();
            byte[] classBytes = cw.toByteArray();
            final Class<?> spinnerClass =
                    SpinnerClassLoader.INSTANCE.defineClass(spinnerClassName, classBytes);
            saveClassFile(spinnerClass, classBytes);
            return (Spinner) spinnerClass.getDeclaredConstructor().newInstance();
        } catch (Throwable t) {
            System.out.println(
                    String.format(
                            "Error constructing spinner class: %s, will return a default spinner.",
                            spinnerClassName));
            return new DefaultSpinner();
        }
    }

    private static void saveClassFile(Class<?> spinnerClass, byte[] bytes) throws IOException {
        File file = new File("target/classes/" + spinnerClass.getName().replace('.', '/') + ".class");
        System.out.println("generated class file:" + file.getAbsolutePath());
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        fileOutputStream.write(bytes);
        fileOutputStream.flush();
        fileOutputStream.close();
    }

    static class SpinnerClassLoader extends ClassLoader {

        static final SpinnerClassLoader INSTANCE;

        static {
            ClassLoader parent = ThreadHelper.Spinner.class.getClassLoader();
            if (parent == null) {
                parent = ClassLoader.getSystemClassLoader();
            }
            INSTANCE = new SpinnerClassLoader(parent);
        }

        SpinnerClassLoader(ClassLoader parent) {
            super(parent);
        }

        Class<?> defineClass(final String name, final byte[] bytes) throws ClassFormatError {
            return defineClass(name, bytes, 0, bytes.length, getClass().getProtectionDomain());
        }
    }
}
